import { extend } from "../share"
let activeEffect 
let shouldTrack

export class ReactiveEffect {
  private _fn: any
  public schedule: any
  deps = []
  active = true
  onStop?:()=>void
  constructor(fn, schedule) {
    this._fn = fn
    this.schedule = schedule
  }
  run() {
    if(!this.active) {
      return this._fn()
    }
    shouldTrack = true
    activeEffect = this
    const result = this._fn()
    shouldTrack = false
    return result
  }
  stop() {
    if(this.active) {
      cleanupEffect(this)
      if(this.onStop) {
        this.onStop()
      }
      this.active = false
    }
  }
}

function cleanupEffect(effect) {
  const {deps} = effect
  if(deps.length){
    for(let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}


const targetMap = new Map()
export function track(target, key) {
  if(!isTracking()) {
    return 
  }
  let depsMap =  targetMap.get(target)
  if(!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if(!dep) {
    dep = new Set()
    depsMap.set(key,dep)
  }
  trackEffect(dep)
}

export function trackEffect(dep) {
  if(dep.has(activeEffect)) {
    return 
  }
  dep.add(activeEffect)
  activeEffect.deps.push(dep) 
}

export function isTracking() {
  return shouldTrack && activeEffect !== undefined
}

export function trigger(target, key) {
  const depsMap =  targetMap.get(target)
  const dep = depsMap.get(key)
  triggerEffect(dep)
}

export function triggerEffect(dep) {
  for(const effect of  dep) {
    if(effect.schedule) {
      effect.schedule()
    } else {
      effect.run()
    }
  }
}



export function effect(fn, option) {
  const _effect = new ReactiveEffect(fn, option.schedule)
  extend(_effect, option)
  _effect.run()
  const runner:any = _effect.run.bind(_effect)
  runner._effect = _effect
  return runner
}

export function stop(runner) {
  const _effect = runner._effect
  _effect.stop()
}